import * as THREE from 'three';
import Base from './core/Base.js';
import Color from '../Color.js';
import Point from '../Point.js';
import { LineGeometry } from 'three/examples/jsm/lines/LineGeometry.js';
import { LineMaterial } from 'three/examples/jsm/lines/LineMaterial.js';
import { Line2 } from 'three/examples/jsm/lines/Line2.js';
import { BufferGeometryUtils } from 'three/examples/jsm/utils/BufferGeometryUtils.js';
// import { MeshLine, MeshLineMaterial } from 'three.meshline';
import { MeshLine, MeshLineMaterial } from './THREE.MeshLine.js'; // THREE.Geometry will be removed from core with r125

class Paths extends Base {
    constructor(root) {
        super();
        this.root = root;
        this.offset = { x: 0, y: 0, z: 0 };
        this.group = null;
        this._cache = {};
        this.geometries = {}; // get ready to merge
    }

    setOffset({ x = 0, y = 0, z = 0 } = {}) {
        this.offset = { x, y, z };
    }

    destory() {
        // console.warn('Paths.destory: ', typeof this.modelsGroup, this.modelsGroup instanceof THREE.Group);
        super.destory();
        this._cache = {};
    }

    checkGroup() {
        if (!this.group) {
            this.group = new THREE.Group();
            this.group.name = 'pathsGroup';
            this.group.rotation.x = Math.PI / -2;
            this.root.scene.add(this.group);
        }
    }

    appendChildren(res) {
        this.checkGroup();
        if (!res || !res.length) return; // TODO: maybe offline, so clear all...
        for (let i = 0, len = res.length; i < len; i++) {
            let o = res[i];
            let g = this.appendChild(o);
            if (g) this.group.add(g);
        }
        this.update(res);
    }
    appendChild(o) {
        if (!o) return;
        const geo = this.getLineGeometry(o);
        
        if (!geo) return;
        let colorCode = Color.rgbToHex(o.color);

        // const mate = new THREE.LineBasicMaterial({ color: 0xff0000, linewidth: 100, });
        // const line = new THREE.Line(geo, mate);
        var material = new LineMaterial({
            color: 0xEFEAEA,
            linewidth: o.width,
            transparent: true,
            opacity: 0.2,
        });
        material.resolution.set(window.innerWidth, window.innerHeight);
        var line = new Line2(geo, material);
        line.computeLineDistances();
        line.scale.set(0.001, 0.001, 0.001);
        // console.log('Paths.appendChild: ', o.id, o, line);
        // console.log('Paths.appendChild: ', o.id, o, positions);
        return line;
    }

    update(res) { //eslint-disable-line
        
    }

    encodeKey(o) {
        let color = Color.rgbToHex(o.color) || '#0b0b0b';
        let lineWidth = (o.width || 200) / 1000;
        let key = `${color}_${lineWidth ? lineWidth : ''}`;
        return key;
    }
    decodeKey(key) {
        let arr = key.split('_');
        return { color: arr[0], lineWidth: arr[1] };
    }

    appendChildrenMerged(res) {
        this.checkGroup();
        if (!res || !res.length) return;

        for (let i = 0, len = res.length; i < len; i++) {
            let o = res[i];
            let key = this.encodeKey(o);
            if (!this.geometries[key]) this.geometries[key] = [];
            this.geometries[key].push(o);
        }
        if (!this.geometries || !Object.keys(this.geometries).length) return;
        // console.warn('Paths.appendChildrenMerged: ', this.geometries);
        Object.keys(this.geometries).forEach(key => {
            let mesh = this.getMergedMesh(key);
            // console.warn('-----> ', mesh);
            if (!mesh) return;
            let g = new THREE.Group();
            g.name = key;
            g.add(mesh);
            this.group.add(g);
        });
    }
    getCpsFromObj(o){
        if (!o || !o.controlPoints || !o.controlPoints.length) return;
        let cps = o.controlPoints;
        if (o.controlPoints.length > 2) cps = Point.getClothoidPoints(cps, 1000) || [];
        if (o.bridges && o.bridges.length) {
            const curve = new THREE.QuadraticBezierCurve3(
                new THREE.Vector3(cps[0].x, cps[0].y, cps[0].z || 0),
                new THREE.Vector3(o.bridges[0].x, o.bridges[0].y, 5000),
                new THREE.Vector3(cps[cps.length - 1].x, cps[cps.length - 1].y, cps[cps.length - 1].z || 0)
            );
            cps = curve.getPoints(50);
        }
        return cps
    }
    getLineGeometry(o){
        let geometry = null;
        let cps = [];
        cps = this.getCpsFromObj(o);
        let splinepts = [];
        cps.forEach(p => {
            splinepts.push(p.x + this.offset.x);
            splinepts.push(p.y + this.offset.y);
            splinepts.push(0);
        });
        geometry = new LineGeometry().setPositions(splinepts);
        return geometry;
    }
    getGeometry(o) {
        let geometry = null;
        let cps = [];
        cps = this.getCpsFromObj(o);
        let splinepts = [];
        cps.forEach(p => {
            splinepts.push(new THREE.Vector3(p.x + this.offset.x, p.y + this.offset.y, p.z + this.offset.z || 0));
        });
        geometry = new THREE.BufferGeometry().setFromPoints(splinepts);
        return geometry;
    }
    getMergedMesh(key) {
        // console.time(`paths.getMergedMesh: ${key}`);
        if (!this.geometries[key] || !this.geometries[key].length) return;
        let { color, lineWidth } = this.decodeKey(key);
        let gs = [];
        this.geometries[key].forEach(o => {
            if (!o.controlPoints || !o.controlPoints.length) return;
            let g = this.getGeometry(o);
            if (!g) return;
            gs.push(g);
        });
        if (!gs.length) return;
        let mergedGeometry = null;
        let mate = null;
        let mesh = null;
        let mls = [];
        gs.forEach(g => {
            let ml = new MeshLine();
            ml.setGeometry(g);
            mls.push(ml);
        });
        mergedGeometry = BufferGeometryUtils.mergeBufferGeometries(mls);
        mate = new MeshLineMaterial({ color, lineWidth });
        mesh = new THREE.Mesh(mergedGeometry, mate);
        mesh.scale.set(0.001, 0.001, 0.001);
        // console.timeEnd(`paths.getMergedMesh: ${key}`);
        return mesh;
    }

    appendLabels({ text, color = '#000000', position = { x: 0, y: 0, z: 0 }, offset = { x: 0, y: 0, z: 0 } } = {}) {
        console.log('Paths.appendLabels: ', text, color, position, offset);
    }
    removeLabels() {
        //
    }

    drawPaths(obj, clearOthers = false) {
        let old = null;
        this.root.pathsGroup.traverse(c => {
            if ((c.isLine || c.isLine2) && c.name === obj.id) old = c;
            if ((c.isLine || c.isLine2) && c.name !== obj.id && clearOthers) this.root.pathsGroup.remove(c);
        });
        if (old) return;
        // console.log(this.root.pathsGroup.children.length);
        let g = this.appendChild(obj);
        this.root.pathsGroup.add(g);
    }
}

export default Paths;
